package com.talkyun.utils.crypt;

import com.talkyun.utils.codec.Base32;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;

import static java.nio.charset.StandardCharsets.UTF_8;

public class StreamCryptService {
    private static final int ALG_LENGTH = 256;

    //* AES/CBC/NoPadding (128)
    //* AES/CBC/PKCS5Padding (128)
    //* AES/ECB/NoPadding (128)
    //* AES/ECB/PKCS5Padding (128)
    //* DES/CBC/NoPadding (56)
    //* DES/CBC/PKCS5Padding (56)
    //* DES/ECB/NoPadding (56)
    //* DES/ECB/PKCS5Padding (56)
    //* DESede/CBC/NoPadding (168)
    //* DESede/CBC/PKCS5Padding (168)
    //* DESede/ECB/NoPadding (168)
    //* DESede/ECB/PKCS5Padding (168)
    //* RSA/ECB/PKCS1Padding (1024, 2048)
    //* RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
    //* RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

    public void encrypt(String algorithm, String key, String inputFile, String outputFile) {
        try {
            this.encrypt(algorithm, key, new FileInputStream(inputFile), new FileOutputStream(outputFile));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public void encrypt(String algorithm, String key, InputStream input, OutputStream output) {
        try {
            this.doEncrypt(algorithm, key, input, output);
        } catch (Exception e) {
            throw new RuntimeException("Encrypt stream error!", e);
        } finally {
            this.close(input);
            this.close(output);
        }
    }

    private void doEncrypt(String algorithm, String key, InputStream input, OutputStream output) throws IOException, GeneralSecurityException {
        Cipher cipher = getCipher(algorithm);
        // 初始化cipher对象，Cipher.ENCRYPT_MODE代表加密
        cipher.init(Cipher.ENCRYPT_MODE, getKey(key, algorithm));

        CipherInputStream cis = new CipherInputStream(input, cipher);

        byte[] buffer = new byte[8192];
        int len = 0;
        while ((len = cis.read(buffer)) > 0) {
            output.write(buffer, 0, len);
        }
        cis.close();
    }

    public void decrypt(String algorithm, String key, String inputFile, String outputFile) {
        try {
            decrypt(algorithm, key, new FileInputStream(inputFile), new FileOutputStream(outputFile));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public void decrypt(String algorithm, String key, InputStream input, OutputStream output) {
        try {
            this.doDecrypt(algorithm, key, input, output);
        } catch (Exception e) {
            throw new RuntimeException("Decrypt stream error!", e);
        } finally {
            this.close(input);
            this.close(output);
        }
    }

    private void doDecrypt(String algorithm, String key, InputStream input, OutputStream output) throws IOException, GeneralSecurityException {
        // 初始化cipher对象，Cipher.DECRYPT_MODE 代表解密
        Cipher cipher = getCipher(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, getKey(key, algorithm));

        CipherOutputStream cos = new CipherOutputStream(output, cipher);
        byte[] buffer = new byte[8192];
        int len;
        while ((len = input.read(buffer)) >= 0) {
            cos.write(buffer, 0, len);
        }
        cos.close();
    }

    private Cipher getCipher(String algorithm) {
        try {
            return Cipher.getInstance(algorithm);
        } catch (Exception e) {
            throw new RuntimeException("Algorithm error!", e);
        }
    }

    public String getKeyPass(String seed, String algorithm) {
        try {
            KeyGenerator gen = KeyGenerator.getInstance(algorithm.toUpperCase());
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.setSeed(seed.getBytes(UTF_8));
            gen.init(ALG_LENGTH, random);
            return Base32.encode(gen.generateKey().getEncoded());
        } catch (GeneralSecurityException se) {
            throw new RuntimeException(se);
        }
    }

    private SecretKey getKey(String key, String algorithm) {
        return new SecretKeySpec(Base32.decode(key), algorithm);

        //        try {
        //            KeyGenerator _generator = KeyGenerator.getInstance(algorithm.toUpperCase());
        //            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        //            random.setSeed(key.getBytes(UTF_8));
        //            _generator.init(ALG_LENGTH, random);
        //            return _generator.generateKey();
        //        } catch (Exception e) {
        //            throw new RuntimeException("Initializing key error! ", e);
        //        }
    }

    private void close(Closeable cls) {
        try {
            cls.close();
        } catch (IOException e) {
            // ignore
            // throw new RuntimeException(e);
        }
    }
}